今天是第二十一天!昨天(第二十天)我們成功幫機器人補齊了最後一塊拼圖——「消息面新聞與情緒分析」,讓我們的 Telegram 機器人擁有了「技術 + 籌碼 + 基本 + 消息」四維度全方位看盤能力!
不過隨著功能越來越多,如果你只是想看個「籌碼數據」或「最新新聞」,卻每次都要接收一長串幾百字的圖文大報告,手機畫面很快就會被塞滿。更麻煩的是,如果每次都要手動打指令(如 /stock 2330),在手機小螢幕上打字總是少了一點順暢感!
今天我們的目標,就是要幫 Telegram 機器人進行 UI/UX 介面大升級!導入 Inline Keyboard 與CallbackQueryHandler,讓你只需動動手指,點擊「技術線圖」、「籌碼動向」、「即時新聞」或「營收數據」,機器人就會立刻為你切換對應的精緻卡片!
步驟一:先釐清邏輯
Telegram 的互動式按鈕運作邏輯非常優雅:
接下來,知道邏輯後,我們就可以來更改程式碼了!
步驟二:完整實作程式碼
我們主要會更改bot.py,並在 main.py 註冊按鈕事件處理器!
import os
import time
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup
from telegram.ext import ContextTypes, CommandHandler, CallbackQueryHandler
from fetcher import get_stock_data
from chart import create_stock_chart
from config import CHAT_ID, DEFAULT_STOCKS
# 建立動態按鈕選單
def get_stock_keyboard(symbol):
keyboard = [
[
InlineKeyboardButton("技術線圖", callback_data=f"chart_{symbol}"),
InlineKeyboardButton("籌碼動向", callback_data=f"chip_{symbol}")
],
[
InlineKeyboardButton("營收數據", callback_data=f"rev_{symbol}"),
InlineKeyboardButton("即時新聞", callback_data=f"news_{symbol}")
]
]
return InlineKeyboardMarkup(keyboard)
# /stock 指令主處理邏輯
async def stock_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
if not context.args:
await update.message.reply_text("請輸入股票代碼,例如:/stock 2330")
return
raw_symbol = context.args[0]
await update.message.reply_text(f"[INFO] 正在獲取 {raw_symbol} 完整資料,請稍後...")
symbol, df, chip_data, revenue_data, news_data = get_stock_data(raw_symbol)
if df is None:
await update.message.reply_text(f"[ERROR] 找不到股票代碼:{raw_symbol}")
return
latest = df.iloc[-1]
summary_text = (
f"【{symbol} 個股儀表板】\n"
f"日期:{df.index[-1].strftime('%Y-%m-%d')}\n"
f"最新收盤價:{latest['Close']:.2f} 元\n\n"
f"請選擇下方按鈕切換詳細看盤維度:"
)
# 發送帶有互動按鈕的主選單
await update.message.reply_text(
text=summary_text,
reply_markup=get_stock_keyboard(symbol)
)
# 按鈕點擊事件處理器 (CallbackQueryHandler)
async def button_callback(update: Update, context: ContextTypes.DEFAULT_TYPE):
query = update.callback_query
await query.answer() # 告知 Telegram 已收到按鈕點擊,消除按鈕加載狀態
data = query.data
action, symbol = data.split("_", 1)
# 重新獲取資料(會優先使用 SQLite 快取)
_, df, chip_data, revenue_data, news_data = get_stock_data(symbol)
if action == "chart":
img_path = create_stock_chart(symbol, df)
with open(img_path, 'rb') as photo:
await query.message.reply_photo(
photo=photo,
caption=f"【{symbol} K線與技術指標線圖】",
reply_markup=get_stock_keyboard(symbol)
)
if os.path.exists(img_path):
os.remove(img_path)
elif action == "chip":
text = (
f"【{symbol} 三大法人籌碼動向】(單位: 張)\n"
f"-----------------------------------\n"
f"外資:{chip_data['foreign']:+d}\n"
f"投信:{chip_data['investment']:+d}\n"
f"自營商:{chip_data['dealer']:+d}\n"
f"-----------------------------------\n"
f"法人合計:{chip_data['total']:+d}\n"
)
await query.message.reply_text(text=text, reply_markup=get_stock_keyboard(symbol))
elif action == "rev":
text = (
f"【{symbol} 最新月營收報告】({revenue_data['month']})\n"
f"-----------------------------------\n"
f"單月營收:{revenue_data['revenue'] / 100000:.2f} 億元\n"
f"MoM (月增率):{revenue_data['mom']:+.1f}%\n"
f"YoY (年增率):{revenue_data['yoy']:+.1f}%\n"
)
await query.message.reply_text(text=text, reply_markup=get_stock_keyboard(symbol))
elif action == "news":
text = f"【{symbol} 即時消息面分析】({news_data['sentiment']})\n-----------------------------------\n"
for idx, item in enumerate(news_data["list"], 1):
text += f"{idx}. {item['title']}\n"
await query.message.reply_text(text=text, reply_markup=get_stock_keyboard(symbol))
def setup_handlers(app):
app.add_handler(CommandHandler("start", lambda u, c: u.message.reply_text("歡迎使用台股看盤小幫手!")))
app.add_handler(CommandHandler("stock", stock_command))
# 註冊按鈕點擊處理器
app.add_handler(CallbackQueryHandler(button_callback))
解釋一下程式碼吧!
InlineKeyboardButton 與 callback_data:
我們為每個按鈕綁定了如 chart_2330.TW 的資料,這樣點擊按鈕時,後台就能精準知道「使用者按了哪一個功能的按鈕」以及「指的是哪一檔股票」。
query.answer():
這是 Telegram Bot API 非常關鍵的小細節!在處理 Callback 時呼叫 await query.answer(),可以讓 Telegram 用戶端手機上的按鈕轉圈圈動畫立刻消失,給使用者最順暢的點擊體驗。
步驟三:執行與測試
在終端機輸入:
python main.py
小結一下:
透過按鈕選單,我們大幅提升了 Telegram Bot 的操作質感,將原本單向的訊息推播,升級成直覺又現代化的互動式 App 體驗!
今天我們成功導入了 Telegram Inline Keyboard 與 CallbackQueryHandler!